//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// The message log for map parsers.
//

# include "StdAfx.h"
# include "Resource.h"
# include "Log.h"
# include "ShowFile.h"
# include "ParseHelpers.h"
# include "Clipboard.h"
# include "Wrappers.h"
# include "Globals.h"

static char * g_strMessageLog;
static size_t g_cMessageLogSize;
static size_t g_cWarningCounter;

typedef std::map<size_t, size_t> wtc_t;
static wtc_t g_WarningTypesCount;

static bool g_bEnableDblClk;

# define WARNING_PREFIX1 "WARNING: "
# define WARNING_PREFIX2 "WARNING (offset "

void ClearLog () {
	delete [] g_strMessageLog;
	g_strMessageLog = NULL;
	g_cMessageLogSize = 0;
	g_cWarningCounter = 0;
	g_WarningTypesCount.clear ();
	g_bEnableDblClk = false;
}

bool IsLogEmpty () {
	return g_cMessageLogSize == 0;
}

size_t GetWarningsCount () {
	return g_cWarningCounter;
}

static
void _ReportTextImpl (const char * _strText) {
	assert (_strText);

	if (! g_strMessageLog) {
		g_cMessageLogSize = ::strlen (_strText);
		g_strMessageLog = new char [g_cMessageLogSize + 1];
		::strcpy (g_strMessageLog, _strText);
	} else {
		assert (g_cMessageLogSize == ::strlen (g_strMessageLog));

		const size_t cMessageLogSize = g_cMessageLogSize + ::strlen (_strText) + 2;
		char * const strMessageLog = new char [cMessageLogSize + 1];

		::strcpy (strMessageLog, g_strMessageLog);
		::strcpy (strMessageLog + g_cMessageLogSize, "\r\n");
		::strcpy (strMessageLog + g_cMessageLogSize + 2, _strText);

		delete [] g_strMessageLog;

		g_strMessageLog = strMessageLog;
		g_cMessageLogSize = cMessageLogSize;
	}
}

static
bool _CanReportWarning (const char * _strText) {
	assert (_strText);

	const size_t cKey = reinterpret_cast<size_t> (_strText);

	const wtc_t::iterator i = g_WarningTypesCount.find (cKey);
	if (i == g_WarningTypesCount.end ()) {
		g_WarningTypesCount.insert (wtc_t::value_type (cKey, 1));
		return true;
	}

	++ i->second;
	return i->second <= 10;
}

void __cdecl ReportWarning (const char * _strText, ...) {
	assert (_strText);

	if (! _CanReportWarning (_strText))
		return;

	++ g_cWarningCounter;
	if (g_cWarningCounter == 200)
		_ReportTextImpl ("ERROR: Too much warnings.");
	if (g_cWarningCounter >= 200)
		return;

	va_list vl;
	va_start (vl, _strText);

	const size_t BUF_LEN = 256;
	char buf [BUF_LEN] = WARNING_PREFIX1;
	const size_t cPrefixLen = ::strlen (buf);
	::_vsnprintf (buf + cPrefixLen, BUF_LEN - 1 - cPrefixLen, _strText, vl);

	va_end (vl);

	_ReportTextImpl (buf);
}

void __cdecl ReportWarning (size_t _cOffset, const char * _strText, ...) {
	assert (_strText);

	g_bEnableDblClk = true;

	if (! _CanReportWarning (_strText))
		return;

	++ g_cWarningCounter;
	if (g_cWarningCounter == 200)
		_ReportTextImpl ("ERROR: Too much warnings.");
	if (g_cWarningCounter >= 200)
		return;

	va_list vl;
	va_start (vl, _strText);

	const size_t BUF_LEN = 256;
	char buf [BUF_LEN];
	if (_cOffset != -1)
		::sprintf (buf, WARNING_PREFIX2 "%Xh): ", _cOffset);
	else
		::strcpy (buf, WARNING_PREFIX1);
	const size_t cPrefixLen = ::strlen (buf);
	::_vsnprintf (buf + cPrefixLen, BUF_LEN - 1 - cPrefixLen, _strText, vl);

	va_end (vl);

	_ReportTextImpl (buf);
}

void __cdecl ReportText (const char * _strText, ...) {
	assert (_strText);

	va_list vl;
	va_start (vl, _strText);

	const size_t BUF_LEN = 256;
	char buf [BUF_LEN];
	::_vsnprintf (buf, BUF_LEN - 1, _strText, vl);

	va_end (vl);

	_ReportTextImpl (buf);
}

INT_PTR CALLBACK _MessageLogProc (HWND _hWnd, UINT _uMsg, WPARAM _wParam, LPARAM);

void ShowMessageLog () {
	DlgBox (IDD_MESSAGE_LOG, g_hWnd, & _MessageLogProc, 0);
}

static WNDPROC g_wpEdit = NULL;

static
LRESULT CALLBACK _EditProc (HWND _hWnd, UINT _uMsg, WPARAM _wParam, LPARAM _lParam) {
	if (_uMsg == WM_LBUTTONDBLCLK) {
		// NOTE: _lParam contains client coordinates of click point.
		// Get index of the nearest line in the edit box.
		const DWORD dwLine = HIWORD (::SendMessage (_hWnd, EM_CHARFROMPOS, 0, _lParam));

		// Get beginning part of the line.
		char strLine [sizeof (WARNING_PREFIX2) + 10];
		* (reinterpret_cast<WORD *> (strLine)) = sizeof (strLine);
		::SendMessage (_hWnd, EM_GETLINE, dwLine, reinterpret_cast<LPARAM> (strLine));

		// Check if it warning line and get source file offset for this warning.
		if (::strncmp (strLine, WARNING_PREFIX2, sizeof (WARNING_PREFIX2) - 1) != 0)
			return 0;
		const size_t cFileOffset = ::ParseHex (strLine + sizeof (WARNING_PREFIX2) - 1);

		// Show the source text.
		ShowFileOffsetBox (_hWnd, g_strFileName, cFileOffset);

		return 0;
	}

	return ::CallWindowProc (g_wpEdit, _hWnd, _uMsg, _wParam, _lParam);
}

static
INT_PTR CALLBACK _MessageLogProc (HWND _hWnd, UINT _uMsg, WPARAM _wParam, LPARAM _lParam) {
	switch (_uMsg) {
		case WM_INITDIALOG: {
			::SendDlgItemMessage (_hWnd, IDC_LOG, WM_SETFONT, (WPARAM) g_hFixedFont, FALSE);

			// Subclass the edit box to hook WM_LBUTTONDBLCLK.
			const HWND hEdit = ::GetDlgItem (_hWnd, IDC_LOG);
			g_wpEdit = reinterpret_cast<WNDPROC> (::GetWindowLong (hEdit, GWL_WNDPROC));
			::SetWindowLong (hEdit, GWL_WNDPROC, reinterpret_cast<long> (_EditProc));

			if (g_cMessageLogSize)
				::SetDlgItemText (_hWnd, IDC_LOG, g_strMessageLog);
			else
				::SetDlgItemText (_hWnd, IDC_LOG, "");

			::ShowWindow (::GetDlgItem (_hWnd, IDC_NOTE_DBLCLK), g_bEnableDblClk ? SW_SHOW : SW_HIDE);

			::SetFocus (hEdit);
			return FALSE;
		}

		case WM_COMMAND:
			switch (LOWORD (_wParam)) {
				case IDC_COPY2: {
					CClipboard clipboard;
					::EmptyClipboard ();

					const HGLOBAL hGlobal = ::GlobalAlloc (GMEM_MOVEABLE, g_cMessageLogSize + 1);
					BYTE * const pData = reinterpret_cast<BYTE *> (::GlobalLock (hGlobal));
					::memcpy (pData, g_strMessageLog, (g_cMessageLogSize + 1)*sizeof (g_strMessageLog [0]));
					::GlobalUnlock (hGlobal);

					::SetClipboardData (CF_TEXT, hGlobal);
					
					return TRUE;
				}

				case IDOK:
				case IDCANCEL:
					// On closing dialog.
					::EndDialog (_hWnd, 0);
					return TRUE;
			}
			break;
	}

	return FALSE;
}
